#include <sys/eventfd.h>
#include <sys/epoll.h>
#include <errno.h>

#include <stdexcept>

#include "executor.hpp"
#include "utils.hpp"

namespace coro {

    Executor::Executor()
        : m_epoll_fd(0), m_event_fd(0)
    {}

    void Executor::register_context(ContextInternal *ctx)
    {
        m_ctxs.push_back(ctx);
    }

    void Executor::run()
    {
        int r = 0;
        const int MAX_EVENTS = 32;
        epoll_event evts[MAX_EVENTS] = {0};

        m_epoll_fd = epoll_create1(EPOLL_CLOEXEC);
        if (m_epoll_fd < 0) {
            throw std::runtime_error((boost::format("failed to create epoll fd: %1%")
                                        % utils::strerror(errno)).str());
        }
        m_event_fd = eventfd(0, EFD_CLOEXEC);
        if (m_event_fd < 0) {
            close(m_epoll_fd);
            throw std::runtime_error((boost::format("failed to create event fd: %1%")
                                        % utils::strerror(errno)).str());
        }
        {
            epoll_event _evt = {0};
            _evt.events = EPOLLIN;
            _evt.data.fd = m_event_fd;
            r = epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, m_event_fd, &_evt);
            if (r < 0) {
                close(m_event_fd);
                close(m_epoll_fd);
                throw std::runtime_error((boost::format("failed to add event fd to epoll: %1%")
                                            % utils::strerror(errno)).str());
            }
        }

        while (true) {
            // Schedule coroutine
            if (m_ctxs.size() == 0) {
                std::cerr << "No more context to schedule." << std::endl;
                break;
            }

            WakedCtxList waked_ctxs;
            {
                std::unique_lock<std::mutex> locker(m_lock);
                waked_ctxs.swap(m_waked_ctxs);
            }

            for (auto &ctx: waked_ctxs) {
                if (!*ctx.first) {
                    remove_ctx(ctx);
                    continue;
                }
                ctx.first->wake(ctx.second);
                if (!*ctx.first) {
                    remove_ctx(ctx);
                }
            }

            // Wait for event
            r = epoll_wait(m_epoll_fd, evts, MAX_EVENTS, 0);
            if (r < 0) {
                if (errno == EINTR) {
                    continue;
                }
                close(m_epoll_fd);
                close(m_event_fd);
                throw std::runtime_error((boost::format("epoll error: %1%")
                                            % utils::strerror(errno)).str());
            } else {
                for (int i = 0; i < r; ++ i) {
                    if (evts[i].data.fd == m_event_fd) {
                        // Just read and do nothing
                        uint64_t _t = 0;
                        read(m_event_fd, (void *)&_t, sizeof(_t));
                    } else {
                        handle_events(evts[i].data.fd, evts[i].events);
                    }
                }
            }
        }

        close(m_epoll_fd);
        close(m_event_fd);
    }

    void Executor::remove_ctx(std::pair<ContextInternal *, std::optional<void *>> &pair)
    {
        // Free data
        void *data = pair.second.value_or(nullptr);
        if (data) {
            pair.first->free_val(data);
            data = nullptr;
        }
        // Remove Context from m_ctxs
        m_ctxs.remove(pair.first);
        // Free Context
        delete pair.first;
    }

    int Executor::io_schedule_internal(int fd, int events, EvtCb &fn)
    {
        int epoll_evt = events;
        int op = 0;
        auto it = m_polling_fds.find(fd);
        if (it == m_polling_fds.end()) {
            m_polling_fds[fd] = std::list<std::pair<int, EvtCb>>();
            op = EPOLL_CTL_ADD;
        } else {
            for (auto &cb: it->second) {
                epoll_evt |= cb.first;
            }
            op = EPOLL_CTL_MOD;
        }

        epoll_event evt = {0};
        evt.events = epoll_evt;
        evt.data.fd = fd;
        int r = epoll_ctl(m_epoll_fd, op, fd, &evt);
        if (r < 0) {
            if (op == EPOLL_CTL_ADD) {
                m_polling_fds.erase(m_polling_fds.find(fd));
            }
            return -errno;
        }

        m_polling_fds[fd].push_back(std::make_pair<>(events, fn));
        return 0;
    }

    void Executor::handle_events(int fd, int event)
    {
        int r = 0;
        auto it = m_polling_fds.find(fd);
        if (it == m_polling_fds.end()) {
            return;
        }
        auto &cbs = it->second;
        using list_iter = decltype(cbs.begin());
        std::vector<list_iter> match_cbs;
        // Find the waked EvtCb
        for (auto list_it = cbs.begin(); list_it != cbs.end(); ++ list_it) {
            if (list_it->first & event) {
                match_cbs.push_back(list_it);
            }
        }
        // Call each cb and remove it from m_polling_fds
        for (auto &cb: match_cbs) {
            cb->second(event);
            cbs.erase(cb);
        }
        match_cbs.clear();

        // Remove fd if no more events is watching
        if (cbs.size() == 0) {
            m_polling_fds.erase(it);
            r = epoll_ctl(m_epoll_fd, EPOLL_CTL_DEL, fd, nullptr);
            if (r < 0) {
                std::cerr << ERROR << "failed to remove fd: "
                          << utils::strerror(errno) << std::endl;
            }
            return;
        }
        int new_event = 0;
        for (auto &cb: cbs) {
            new_event |= cb.first;
        }
        epoll_event evt = {0};
        evt.events = new_event;
        evt.data.fd = fd;
        r = epoll_ctl(m_epoll_fd, EPOLL_CTL_MOD, fd, &evt);
        if (r < 0) {
            std::cerr << ERROR << "failed to modify fd: "
                      << utils::strerror(errno) << std::endl;
        }
    }
};
